#include "luster_c.hh"

#include <assert.h>
#include <inttypes.h>
#include <stdint.h>

#include "luster_lexpr.hh"
#include "luster_log.hh"
#include "luster_status.hh"
#include "luster_string.hh"

namespace luster {

luster_define_dynarr(clist, citem_t);

static void ctype_fmt(ctype_t const *ctype, StringBuffer &sb);

ctype_t *ctype_init_const(ctype_t *ctype) {
	ctype->tag = CTYPE_CONST;
	ctype->u.type = (ctype_t *)allocate(sizeof(ctype_t));
	return ctype->u.type;
}

ctype_t *ctype_init_ptr(ctype_t *ctype) {
	ctype->tag = CTYPE_PTR;
	ctype->u.type = (ctype_t *)allocate(sizeof(ctype_t));
	return ctype->u.type;
}

bool ctype_eq(ctype_t lhs, ctype_t rhs) {
	if (lhs.tag != rhs.tag)
		return false;

	switch (lhs.tag) {
	case CTYPE_CONST:
	case CTYPE_PTR:
		return ctype_eq(*lhs.u.type, *rhs.u.type);

	case CTYPE_ARR:
		return lhs.u.array.len == rhs.u.array.len &&
			ctype_eq(*lhs.u.array.element_type,
				*rhs.u.array.element_type);
	default:
		return true;
	}
}

void ctype_deinit(ctype_t *ctype) {
	switch (ctype->tag) {
	case CTYPE_IDK:
	case CTYPE_INT:
	case CTYPE_UINT:
	case CTYPE_ISIZE:
	case CTYPE_USIZE:
	case CTYPE_BOOL:
	case CTYPE_CHAR:
		break;
	case CTYPE_CONST:
	case CTYPE_PTR:
		ctype_deinit(ctype->u.type);
		deallocate(ctype->u.type);
		break;
	case CTYPE_ARR:
		ctype_deinit(ctype->u.array.element_type);
		deallocate(ctype->u.array.element_type);
		break;
	default:
		logfatal("TODO: deinit ctype with tag: %d", ctype->tag);
	}

	ctype->tag = CTYPE_IDK;
}

char const *citem_type_name(citem_tag_t ctype) {
	static char const *name[] = {
#define _(M_NAME) "CITEM_" #M_NAME,
		CITEM_TYPE_MAP(_)
#undef _
	};

	return name[ctype];
}

void clist_deinit_rec(clist_t *ls) {
	if (ls->cap == 0) {
		return;
	}

	for (uintptr_t i = 0; i < ls->len; ++i) {
		citem_deinit(ls->elements + i);
	}

	clist_deinit(ls);
}

void citem_deinit(citem_t *citem) {
	switch (citem->tag) {
	case CITEM_I32:
	case CITEM_F32:
	case CITEM_BOOL:
	case CITEM_STRING:
	case CITEM_VAR:
		break;

	case CITEM_ADD:
	case CITEM_SUB:
	case CITEM_MUL:
	case CITEM_DIV:

	case CITEM_AND:
	case CITEM_OR:
	case CITEM_NOT:
	case CITEM_LET:
		clist_deinit_rec(&citem->u.clist);
		break;

	default:
		logfatal("TODO: deinit c-expr, type: %d, type name: %s",
			citem->tag, citem_type_name(citem->tag));
	}

	citem->tag = CITEM_I32;
	citem->u.i32 = -1;

	ctype_deinit(&citem->type);
}

static Status translate_type(translator_t *t, Lexpr *lexpr, ctype_t *ctype);

static Status translate_complex_type(
	translator_t *t, Lexpr *lexpr, ctype_t *ctype) {

	if (lexpr->as.exprs.len == 0) {
		logprint(LOG_ERROR, "can not parse empty list to a type of C");
		return STATUS_ERROR;
	}

	Lexpr *func = lexpr->as.exprs.data + 0;

	if (func->tag != LEXPR_SYMBOL) {
		logprint(
			LOG_ERROR, "first element of type call must be symbol");
		return STATUS_ERROR;
	}

	if (func->as.string == "const") {
		if (lexpr->as.exprs.len != 2) {
			logprint(LOG_ERROR, "(const ..) need 1 argument");
			return STATUS_ERROR;
		}

		ctype_t *inner_type = ctype_init_const(ctype);

		return translate_type(t, lexpr->as.exprs.data + 1, inner_type);
	}

	if (func->as.string == "ptr") {
		if (lexpr->as.exprs.len != 2) {
			fprintf(stderr, "type: ");
			lexpr_display(lexpr, stderr);
			fprintf(stderr, "\n");

			logprint(LOG_ERROR,
				"(ptr ..) need 1 argument, but found %zu",
				func->as.exprs.len);
			return STATUS_ERROR;
		}

		ctype_t *inner_type = ctype_init_ptr(ctype);

		return translate_type(t, lexpr->as.exprs.data + 1, inner_type);
	}

	logfatal("TODO: ");
}

static Status translate_basic_type(
	translator_t *t__, Lexpr *lexpr, ctype_t *ctype) {
	luster_UNUSED(t);

	if (lexpr->as.string == "int") {
		ctype->tag = CTYPE_INT;
		return STATUS_OK;
	}

	if (lexpr->as.string == "bool") {
		ctype->tag = CTYPE_BOOL;
		return STATUS_OK;
	}

	if (lexpr->as.string == "char") {
		ctype->tag = CTYPE_CHAR;
		return STATUS_OK;
	}

	logprint(LOG_ERROR,
		"can not parse symbol as a basic type of C, symbol: "
		"%.*s",
		(int)lexpr->as.string.len, lexpr->as.string.data);

	return STATUS_ERROR;
}

static Status translate_type(translator_t *t, Lexpr *lexpr, ctype_t *ctype) {

	if (lexpr->tag == LEXPR_LIST) {
		return translate_complex_type(t, lexpr, ctype);
	}

	if (lexpr->tag == LEXPR_SYMBOL) {
		return translate_basic_type(t, lexpr, ctype);
	}

	logprint(LOG_ERROR, "can not translate %s to a type",
		lexpr_tag_name(lexpr->tag));
	return STATUS_ERROR;
}

/*
	(let
		a: int = 10;
		b: int = (+ a b);
	)

	(let x: int = 10)
*/
static Status translate_let(translator_t *t, Vec<Lexpr> *ll, citem_t *citem) {
	citem->tag = CITEM_LET;
	citem->u.clist = (clist_t){0};

	clist_t *clist = &citem->u.clist;
	Status status;

	bool first = true;

	for (uintptr_t i = 1; i < ll->len;) {
		/* 分号 */

		if (first) {
			first = false;
		} else {
			if (ll->data[i].tag != LEXPR_OP ||
				ll->data[i].as.string != ';') {
				logprint(LOG_ERROR,
					"let need a `;' at %" PRIuPTR
					"-th element of list",
					i);
				return STATUS_ERROR;
			}
			i += 1;

			/* 最后一个分号。*/
			if (i >= ll->len) {
				break;
			}
		}

		/* 变量名 */

		if (ll->data[i].tag != LEXPR_SYMBOL) {
			logprint(LOG_ERROR,
				"let need a symbol at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		citem_t *cvar = clist_append(clist);

		cvar->tag = CITEM_VAR;
		cvar->u.string = ll->data[i].as.string;

		i += 1;

		/* 冒号 */

		if (i >= ll->len || ll->data[i].tag != LEXPR_OP ||
			ll->data[i].as.string != ":") {
			logprint(LOG_ERROR,
				"let need a `:' at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		i += 1;

		/* 类型 */

		if (i >= ll->len) {
			logprint(LOG_ERROR,
				"let need a symbol at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		status = translate_type(t, ll->data + i, &cvar->type);
		if (status != STATUS_OK) {
			return status;
		}

		i += 1;

		/* 等号 */

		if (i >= ll->len || ll->data[i].tag != LEXPR_OP ||
			ll->data[i].as.string != "=") {
			logprint(LOG_ERROR,
				"let need a `=' at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		i += 1;

		/* 值 */

		if (i >= ll->len) {
			logprint(LOG_ERROR,
				"let need a l-expr at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		citem_t *cval = clist_append(clist);

		status = translate(t, ll->data + i, cval);
		if (status != STATUS_OK) {
			return status;
		}

		if (!citem_is_expr(cval->tag)) {
			logprint(LOG_ERROR,
				"`let' need a c-expr at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		if (!ctype_eq(cvar->type, cval->type)) {
			if (cvar->type.tag == CTYPE_IDK) {
				logprint(LOG_ERROR,
					"let: I don't known type of variable");
				return STATUS_ERROR;
			}

			if (cval->type.tag == CTYPE_IDK) {
				logprint(LOG_ERROR,
					"let: I don't known type of value");
				return STATUS_ERROR;
			}

			StringBuffer sb_val;
			StringBuffer sb_var;

			ctype_fmt(&cval->type, sb_val);
			ctype_fmt(&cvar->type, sb_var);

			LOG(ERROR) << "`let' try to bind a value with type `"
				   << sb_val << "' to variable with type `"
				   << sb_var << "'" << LOGEND;

			return STATUS_ERROR;
		}

		i += 1;
	}

	return STATUS_OK;
}

static bool is_operator(Lexpr const *lexpr, char const *op) {
	return lexpr->tag == LEXPR_OP && lexpr->as.string == op;
}

/*
	(set
		a: int = 10;
		b: int = (+ a b);
	)

	(set x: int = 10)
*/
static Status translate_set(translator_t *t, Vec<Lexpr> *ll, citem_t *citem) {
	citem->tag = CITEM_SET;
	citem->u.clist = (clist_t){0};

	clist_t *clist = &citem->u.clist;
	Status status;

	bool first = true;

	for (uintptr_t i = 1; i < ll->len;) {
		/* 分号 */

		if (first) {
			first = false;
		} else {
			if (!is_operator(ll->data + i, ";")) {
				logprint(LOG_ERROR,
					"`set' need a `;' at %" PRIuPTR
					"-th element of list",
					i);
				return STATUS_ERROR;
			}
			i += 1;

			/* 最后一个分号。*/
			if (i >= ll->len) {
				break;
			}
		}

		/* 变量名。

		TODO: 支持复杂赋值。
		*/

		if (ll->data[i].tag != LEXPR_SYMBOL) {
			logprint(LOG_ERROR,
				"`set' need a symbol at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		citem_t *cvar = clist_append(clist);

		cvar->tag = CITEM_VAR;
		cvar->u.string = ll->data[i].as.string;

		i += 1;

		/* 等号 */

		if (i >= ll->len || ll->data[i].tag != LEXPR_OP ||
			ll->data[i].as.string != "=") {
			logprint(LOG_ERROR,
				"`set' need a `=' at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		i += 1;

		/* 值 */

		if (i >= ll->len) {
			logprint(LOG_ERROR,
				"`set' need a l-expr at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		citem_t *cval = clist_append(clist);

		status = translate(t, ll->data + i, cval);
		if (status != STATUS_OK) {
			return status;
		}

		if (!citem_is_expr(cval->tag)) {
			logprint(LOG_ERROR,
				"`set' need a c-expr at %" PRIuPTR
				"-th element of list",
				i);
			return STATUS_ERROR;
		}

		/* 获取变量的类型。*/

		if (!ctype_eq(cvar->type, cval->type)) {
			if (cvar->type.tag == CTYPE_IDK) {
				logprint(LOG_ERROR,
					"set: I don't known type of variable");
				return STATUS_ERROR;
			}

			if (cval->type.tag == CTYPE_IDK) {
				logprint(LOG_ERROR,
					"set: I don't known type of value");
				return STATUS_ERROR;
			}

			StringBuffer sb_val;
			StringBuffer sb_var;

			luster_OWN(sb_val);
			luster_OWN(sb_var);

			ctype_fmt(&cval->type, sb_val);
			ctype_fmt(&cvar->type, sb_var);

			LOG(ERROR) << "`set' try to bind a value with type `"
				   << sb_val << "' to variable with type `"
				   << sb_var << "'" << LOGEND;

			return STATUS_ERROR;
		}

		i += 1;
	}

	return STATUS_OK;
}

static Status translate_llist(translator_t *t, Vec<Lexpr> *ll, citem_t *citem) {
	if (ll->len == 0) {
		logfatal("TODO: parse empty list");
	}

	Lexpr *func = ll->data + 0;

	if (func->tag == LEXPR_OP) {
		if (func->as.string == '+') {
			citem->tag = CITEM_ADD;
		} else if (func->as.string == '-') {
			citem->tag = CITEM_SUB;
		} else if (func->as.string == '*') {
			citem->tag = CITEM_MUL;
		} else if (func->as.string == '/') {
			citem->tag = CITEM_DIV;
		} else {
			logfatal("TODO: translate operator: %.*s",
				(int)func->as.string.len, func->as.string.data);
		}
		citem->type.tag = CTYPE_INT;
	} else if (func->tag == LEXPR_SYMBOL) {
		logprint(LOG_DEBUG, "get symbol call");

		if (func->as.string == "let") {
			return translate_let(t, ll, citem);
		}

		if (func->as.string == "set") {
			return translate_set(t, ll, citem);
		}

		if (func->as.string == "and") {
			logprint(LOG_DEBUG, "get and expr");
			citem->tag = CITEM_AND;

			if (ll->len < 3) {
				logprint(LOG_ERROR,
					"operator and need at least 2 "
					"arguments");
				return STATUS_ARGS_COUNT_ERR;
			}
		} else if (func->as.string == "or") {
			logprint(LOG_DEBUG, "get or expr");
			citem->tag = CITEM_OR;

			if (ll->len < 3) {
				logprint(LOG_ERROR,
					"operator or need at least 2 "
					"arguments");
				return STATUS_ARGS_COUNT_ERR;
			}
		} else if (func->as.string == "not") {
			logprint(LOG_DEBUG, "get not expr");
			citem->tag = CITEM_NOT;

			if (ll->len != 2) {
				logprint(LOG_ERROR,
					"operator not need 1 arguments");
				return STATUS_ARGS_COUNT_ERR;
			}
		} else {
			logfatal("TODO: translate function: %.*s",
				(int)func->as.string.len, func->as.string.data);
		}

		citem->type.tag = CTYPE_BOOL;
	}

	citem->u.clist = (clist_t){0};

	for (uintptr_t i = 1; i < ll->len; ++i) {
		citem_t *new_citem = clist_append(&citem->u.clist);

		Status status = translate(t, ll->data + i, new_citem);
		if (status != STATUS_OK) {
			return status;
		}
	}

	return STATUS_OK;
}

Status translate(translator_t *t, Lexpr *lexpr, citem_t *citem) {
	switch (lexpr->tag) {
	case LEXPR_S32:
		citem->tag = CITEM_I32;
		citem->u.i32 = lexpr->as.i32;
		citem->type.tag = CTYPE_INT;
		return STATUS_OK;

	case LEXPR_BOOL:
		citem->tag = CITEM_BOOL;
		citem->u.yes = lexpr->as.yes;
		citem->type.tag = CTYPE_BOOL;
		return STATUS_OK;

	case LEXPR_SYMBOL:
		citem->tag = CITEM_VAR;
		citem->u.string = lexpr->as.string;
		citem->type.tag = CTYPE_IDK;
		return STATUS_OK;

	case LEXPR_STRING:
		citem->tag = CITEM_STRING;
		citem->u.string = lexpr->as.string;

		ctype_init_const(ctype_init_ptr(&citem->type))->tag =
			CTYPE_CHAR;
		return STATUS_OK;

	case LEXPR_LIST:
		citem->type.tag = CTYPE_IDK;
		return translate_llist(t, &lexpr->as.exprs, citem);

	default:
		logfatal("TODO: translate more kinds of l-expr to c-expr, "
			 "l-expr type: %d, type name: %s",
			lexpr->tag, lexpr_tag_name(lexpr->tag));
	}
}

static void write_string(Slice s, FILE *fp) {
	fwrite(s.data, s.len, 1, fp);
}

static void ctype_fmt(ctype_t const *ctype, StringBuffer &sb) {
	StringBuffer result;

	switch (ctype->tag) {
	case CTYPE_INT:
		stringbuf_append_cstring(&result, "int ");
		stringbuf_append_stringbuf(&result, sb);
		break;
	case CTYPE_UINT:
		stringbuf_append_cstring(&result, "unsigned int ");
		stringbuf_append_stringbuf(&result, sb);
		break;
	case CTYPE_BOOL:
		stringbuf_append_cstring(&result, "bool ");
		stringbuf_append_stringbuf(&result, sb);
		break;
	case CTYPE_CHAR:
		stringbuf_append_cstring(&result, "char ");
		stringbuf_append_stringbuf(&result, sb);
		break;
	case CTYPE_CONST:
		stringbuf_append_cstring(&result, "const ");
		stringbuf_append_stringbuf(&result, sb);

		ctype_fmt(ctype->u.type, result);
		break;
	case CTYPE_PTR:
		stringbuf_append_cstring(&result, "*");
		stringbuf_append_stringbuf(&result, sb);

		ctype_fmt(ctype->u.type, result);
		break;

	default:
		logfatal("TODO: format c-type with tag: %d", ctype->tag);
	}

	stringbuf_clear(&sb);
	sb = result;
}

static void display_val_decl(citem_t const *cvar, FILE *fp) {
	if (cvar->tag != CITEM_VAR) {
		logfatal("unexpected element type");
	}

	logprint(LOG_DEBUG, "display var decl: `%.*s'", (int)cvar->u.string.len,
		cvar->u.string.data);

	StringBuffer display_buf;
	luster_OWN(display_buf);

	stringbuf_append_slice(&display_buf, cvar->u.string);

	ctype_fmt(&cvar->type, display_buf);
	fputs(stringbuf_get(&display_buf), fp);
}

static void display_let(FILE *fp, clist_t const *cl, int indent) {
	if (cl->len % 2 != 0) {
		logfatal("invalid length of let");
	}

	for (uintptr_t i = 0; i < cl->len; i += 2) {
		citem_t const *cvar = cl->elements + i + 0;
		citem_t const *cval = cl->elements + i + 1;

		/* 缩进 */

		for (int i = 0; i < indent; ++i) {
			fputs("\t", fp);
		}

		/* 变量声明 */

		display_val_decl(cvar, fp);

		/* 等号 */

		fputs(" = ", fp);

		/* 值 */

		citem_display(cval, fp, .need_bracket = false);

		/* 分号 */

		fputs(";\n", fp);
	}
}

static void citem_display_unary(
	FILE *fp, char const *op, clist_t const *ls, bool need_bracket) {
	if (ls->len != 1) {
		logfatal("invalid number of arguments for unary operator, "
			 "operator: %s, argument counts: %" PRIuPTR,
			op, ls->len);
	}

	if (need_bracket) {
		fputs("(", fp);
	}

	fputs(op, fp);
	citem_display(ls->elements + 0, fp, .need_bracket = true);

	if (need_bracket) {
		fputs(")", fp);
	}
}

static void citem_display_binop(
	FILE *fp, clist_t const *ls, char const *op, bool need_bracket) {

	if (need_bracket) {
		fputs("(", fp);
	}

	bool first = true;
	for (uintptr_t i = 0; i < ls->len; ++i) {
		if (first) {
			first = false;
		} else {
			fputs(op, fp);
		}
		citem_display(ls->elements + i, fp, .need_bracket = true);
	}

	if (need_bracket) {
		fputs(")", fp);
	}
}

Status citem_display_opt(citem_t const *c, FILE *fp, citem_display_opt_t opt) {
	switch (c->tag) {
	case CITEM_I32:
		fprintf(fp, "%" PRIi32, c->u.i32);
		return STATUS_OK;

	case CITEM_BOOL:
		fputs(c->u.yes ? "true" : "false", fp);
		return STATUS_OK;

	case CITEM_VAR:
		fwrite(c->u.string.data, c->u.string.len, 1, fp);
		return STATUS_OK;

	case CITEM_ADD:
		citem_display_binop(fp, &c->u.clist, " + ", opt.need_bracket);
		return STATUS_OK;

	case CITEM_SUB:
		citem_display_binop(fp, &c->u.clist, " - ", opt.need_bracket);
		return STATUS_OK;

	case CITEM_MUL:
		citem_display_binop(fp, &c->u.clist, " * ", opt.need_bracket);
		return STATUS_OK;

	case CITEM_DIV:
		citem_display_binop(fp, &c->u.clist, " / ", opt.need_bracket);
		return STATUS_OK;

	case CITEM_AND:
		citem_display_binop(fp, &c->u.clist, " && ", opt.need_bracket);
		return STATUS_OK;

	case CITEM_OR:
		logprint(LOG_DEBUG, "or: arguments: %d", c->u.clist.len);

		citem_display_binop(fp, &c->u.clist, " || ", opt.need_bracket);
		return STATUS_OK;

	case CITEM_NOT:
		citem_display_unary(fp, "!", &c->u.clist, opt.need_bracket);
		return STATUS_OK;

	case CITEM_LET:
		display_let(fp, &c->u.clist, 0);
		return STATUS_OK;

	case CITEM_STRING:
		/* TODO：转义字符。*/
		fputc('"', fp);
		write_string(c->u.string, fp);
		fputc('"', fp);
		return STATUS_OK;

	default:
		logfatal("TODO: display more citem");
	}
}

} // namespace luster
